/* Copyright 2017 - 2025 R. Thomas
 * Copyright 2017 - 2025 Quarkslab
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <cstring>

#include "LIEF/MachO/Binary.h"
#include "LIEF/MachO/Parser.hpp"
#include "LIEF/MachO/FatBinary.hpp"
#include <iostream>

#include "Binary.hpp"
#include "LIEF/Abstract/json.hpp"
#include "LIEF/jsonn.hpp"
#include "LIEF/MachO/Relocation.hpp"
#include "LIEF/MachO/Section.hpp"
#include "LIEF/MachO/SegmentCommand.hpp"
#include "LIEF/MachO/Symbol.hpp"
using namespace LIEF::MachO;

Macho_Binary_t** macho_parse(const char *file) {
  FatBinary* fat = Parser::parse(file).release();
  if (fat == nullptr) {
    return nullptr;
  }

  size_t nb_bin = fat->size();

  auto** c_macho_binaries = static_cast<Macho_Binary_t**>(
      malloc((fat->size() + 1) * sizeof(Macho_Binary_t**)));

  for (size_t i = 0; i < nb_bin; ++i) {
    Binary* binary = fat->at(i);
    if (binary != nullptr) {
      c_macho_binaries[i] = static_cast<Macho_Binary_t*>(malloc(sizeof(Macho_Binary_t)));
      init_c_binary(c_macho_binaries[i], binary);
    }
  }

  fat->release_all_binaries();

  c_macho_binaries[nb_bin] = nullptr;
  delete fat;

  return c_macho_binaries;
}


using namespace LIEF::MachO;
using json = nlohmann::json;

nlohmann::basic_json<> print_binary(const Binary &binary) {
    json jj;
    json jsonH;
    const Header &header = binary.header();
    jj["header"] = {

    };
    json jsonSegments = json::array();

    auto segments = binary.segments();
    for (const SegmentCommand &segment: binary.segments()) {
        // jsonSegments[i] = segment;
        json t;
        t["name"] = segment.name();
        t["virtual_address"] = segment.virtual_address();
        t["virtual_size"] = segment.virtual_size();
        t["content"] = segment.content();
        json t1 = json::array();
        for (const Relocation &relocation: segment.relocations()) {
            json t;
            t["type"] = relocation.type();
            t["address"] = relocation.address();
            t1.push_back(t);
        }
        t["relocations"] = t1;
        jsonSegments.push_back(t);
    }
    jj["segments"] = jsonSegments;
    jj["imagebase"] = binary.imagebase();
    // std::cout <<  << '\n';
    json jsonLibrary = json::array();

    // std::cout << "== Library ==" << '\n';
    // for (const DylibCommand& library : binary.libraries()) {
    //   std::cout << library << '\n';
    // }
    jj["library"] = jsonLibrary;
    // std::cout << '\n';
    json jsonSec;
    // std::cout << "== Sections ==" << '\n';
    json sections = json::object();

    for (const Section &section: binary.sections()) {
        json t;
        if (sections.contains(section.segment_name())) {
            t = sections[section.segment_name()];
        }else {
            t = json::array();
        }
        json t2;
        t2["name"] = section.name();
        t2["virtual_address"] = section.virtual_address();
        t2["size"] = section.size();
        t.push_back(t2);
        sections[section.segment_name()] = t;
        // std::cout << section << '\n';
    }
    jj["sections"] = sections;;
    json jsonCmd;
    for (const LoadCommand &cmd: binary.commands()) {
        // std::cout << cmd << '\n';
        // std::cout << "======================" << '\n';
    }
    json jsonSymbols = json::array();
    // std::cout << "== Symbols ==" << '\n';
    int a = 0;
    json jsonBindings = json::array();
    for (const Symbol &symbol: binary.symbols()) {
        // std::cout << symbol << '\n';
        json t;
        t["name"] = symbol.name();
        t["value"] = symbol.value();
        t["type"] = symbol.type();
        if (symbol.library_ordinal() > 0) {
            a = a + symbol.library_ordinal();
            const BindingInfo* binding = symbol.binding_info();
            json t2;
            t2["address"] = binding->address();
            t2["addend"] = binding->addend();
            t2["symbol"] = json{
                {"name",binding->symbol()->name()}
            };
            jsonBindings.push_back(t2);
        }

        jsonSymbols.push_back(t);
    }
    // std::cout << "libinfo " << a << '\n';

    // std::cout << "symbols size " << jsonSymbols.size() << '\n';
    jj["symbols"] = jsonSymbols;
    jj["bindings"] = jsonBindings;
    json jsonExports;
    // std::cout << "== Exported symbols ==" << '\n';
    for (const Symbol &symbol: binary.exported_symbols()) {
        // std::cout << symbol << '\n';
    }
    json jsonImports;
    // std::cout << "== Imported symbols ==" << '\n';
    for (const Symbol &symbol: binary.imported_symbols()) {
        // std::cout << symbol << '\n';
    }

    json jsonRelocations;
    // std::cout << "== Relocations ==" << '\n';
    for (const Relocation &relocation: binary.relocations()) {
        // std::cout << relocation << '\n';
    }

    return jj;
}


const char* macho_toJson(const char *file, const char *res, int64_t* len) {
  // Macho_Binary_t** m = macho_parse(file);
  std::unique_ptr<FatBinary> binaries = Parser::parse(file);
    macho_parse(file);
    json j = json::array();
    std::cout << "bin size " << binaries->size() << '\n';
    for (const Binary &binary: *binaries) {
        nlohmann::basic_json<> r = print_binary(binary);
        std::cerr << r << '\n';
        const char* dump = r.dump().c_str();
        *len = strlen(dump);
        res = dump;
        return res;
    }
  // if (m == nullptr) {
  //   return "{\"error\":\"not parse\"}";
  // }
  // for (int i = 0; i < sizeof(m); ++i) {
  //   Macho_Binary_t* b = m[i];
  //   Macho_Symbol_t** symbols = b->symbols;
  //   std::cout << i << '\n';
  // }
  // LIEF::to_json_from_abstract(LIEF::Object(m));
    const char* dump = j.dump().c_str();

  return res;
}

